#include "limbo/errc.h"
#include "limbo/ip/state.hpp"
#include "limbo/slip/state.hpp"
#include "limbo/stack.hpp"
#include "limbo/udp/state.hpp"
#include "test-utils.h"

using namespace limbo;

using SLIP = slip::State<void>;
using IP = ip::State<SLIP, ip::Opts::calculate_checksum>;
using UDP = udp::State<IP>;
using MyStack = Stack<Layer<UDP>, Layer<IP>, Layer<SLIP>>;

auto src = make_address("10.0.31.124");
auto dst = make_address("224.0.0.251");

char payload[] = {'h', 'e', 'l', 'l', 'o'};
auto payload_chunk = Chunk(payload, sizeof(payload));

TEST_CASE("send-n-receive", "[ip+udp+slip]") {
  auto stack = MyStack();
  auto &ip_state = stack.get<1, 0>();
  auto &udp_state = stack.get<0, 0>();
  ip_state.init(0x04);

  auto link_ctx = SLIP::Context{};
  auto ip_ctx = IP::Context{src, dst, 6, ip::Proto::udp, 0, 0, &link_ctx};
  auto udp_ctx = UDP::Context{7, 8, &ip_ctx};
  unsigned char buff[50];
  auto buff_chunk = Chunk(buff, sizeof(buff));
  auto result = stack.send(buff_chunk, udp_ctx, payload_chunk);
  REQUIRE(result);
  REQUIRE(!result.demand());

  auto link_chunk = result.consumed();

  result = stack.recv(link_chunk, nullptr);
  REQUIRE(result);
  REQUIRE(!result.demand());
  REQUIRE(result.state() == &udp_state);

  auto &p = udp_state.get_parsed();
  CHECK(p.source_port == 7);
  CHECK(p.dest_port == 8);
  CHECK(p.length == payload_chunk.size() + 8);
  CHECK(p.payload == payload_chunk);
  CHECK(p.container->source == src);
  CHECK(p.container->destination == dst);
}

TEST_CASE("real-world sample", "[ip+udp+slip]") {
  unsigned char buff[] = {
      0x45, 0x00, 0x00, 0x22, 0xd3, 0x09, 0x40, 0x00, 0x40, 0x11, 0x11, 0xc2,
      0xa9, 0xfe, 0x01, 0x01, 0xa9, 0xfe, 0x01, 0x02, 0xc2, 0x56, 0x04, 0xd2,
      0x00, 0x0e, 0x9e, 0xcd, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0a, 0xc0,
  };
  auto buff_chunk = Chunk(buff, sizeof(buff));
  char payload[] = {'h', 'e', 'l', 'l', 'o', 10};
  auto payload_chunk = Chunk(payload, sizeof(payload));
  auto stack = MyStack();
  auto &udp_state = stack.get<0, 0>();

  auto result = stack.recv(buff_chunk, nullptr);
  REQUIRE(result);
  REQUIRE(!result.demand());
  REQUIRE(result.state() == &udp_state);

  auto src = make_address("169.254.1.1");
  auto dst = make_address("169.254.1.2");

  auto &p = udp_state.get_parsed();
  CHECK(p.source_port == 49750);
  CHECK(p.dest_port == 1234);
  CHECK(p.payload == payload_chunk);
  CHECK(p.container->source == src);
  CHECK(p.container->destination == dst);
}
